// Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "VmiTcpClientSocket.h"
#include <arpa/inet.h>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <chrono>
#include <thread>
#include <endian.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <unistd.h>
#include "HWSecure/include/securec.h"
#include "Log/logging.h"

using namespace std;

namespace {
    const unsigned int SLEEP_INTERVAL_MS = 500;
    const int RETRY_TIMES = 3;
}

/*
 * @功能描述：TCP客户端套接字构造函数
 * @参数 [in] remoteIp：远端IP
 * @参数 [in] remotePort：远端端口
 */
VmiTcpClientSocket::VmiTcpClientSocket(unsigned int remoteIp, unsigned short int remotePort)
    : VmiTcpSocket(INVALID_FD, VmiSocketAddressGroup(remoteIp, remotePort, 0, 0),
      SOCKET_OPTION_BITSET_NODELAY | SOCKET_OPTION_BITSET_QUICKACK)
{
}

/*
 * @功能描述：TCP客户端套接字构造函数
 * @参数 [in] remoteIp：远端IP
 * @参数 [in] remotePort：远端端口
 * @参数 [in] localIp：本地IP，可选参数
 * @参数 [in] localPort：本地端口，可选参数
 */
VmiTcpClientSocket::VmiTcpClientSocket(unsigned int remoteIp, unsigned short int remotePort, unsigned int localIp,
    unsigned short int localPort)
    : VmiTcpSocket(INVALID_FD, VmiSocketAddressGroup(remoteIp, remotePort, localIp, localPort),
      SOCKET_OPTION_BITSET_NODELAY | SOCKET_OPTION_BITSET_QUICKACK)
{
}

/*
 * @功能描述：TCP客户端套接字构造函数
 * @参数 [in] socketOption：套接字选项
 * @参数 [in] remoteIp：远端IP
 * @参数 [in] remotePort：远端端口
 * @参数 [in] localIp：本地IP，可选参数
 * @参数 [in] localPort：本地端口，可选参数
 */
VmiTcpClientSocket::VmiTcpClientSocket(unsigned int socketOption, unsigned int remoteIp, unsigned short int remotePort,
    unsigned int localIp, unsigned short int localPort)
    : VmiTcpSocket(INVALID_FD, VmiSocketAddressGroup(remoteIp, remotePort, localIp, localPort), socketOption)
{
}

/*
 * @功能描述：设置socket参数选项
 * @参数 [in] socketFd：socket句柄
 * @返回值：成功返回0，小于0表示失败
 */
int VmiTcpClientSocket::SetOptions(int socketFd)
{
    int reuse = 1;
    // 端口复用
    if (setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
        ERR("setsockopt REUSER failed errno: (%d): %s", errno, strerror(errno));
        return SOCK_RET_FAILED;
    }

    // close()立刻返回，但不会发送未发送完成的数据，而是通过一个REST包强制的关闭socket描述符，即强制退出
    /*lint -save -e529 */
    struct linger tcplinger = {
        .l_onoff = 1,
        .l_linger = 0
    };/*lint -restore */
    if (setsockopt(socketFd, SOL_SOCKET, SO_LINGER, &tcplinger, sizeof(tcplinger)) < 0) {
        ERR("setsockopt LINGER failed errno: (%d): %s", errno, strerror(errno));
        return SOCK_RET_FAILED;
    }

    struct timeval timeout = {
        .tv_sec = 1,
        .tv_usec = 0
    };
    // 设置发送超时1s
    if (setsockopt(socketFd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) {
        ERR("setsockopt SO_SNDTIMEO failed errno: (%d): %s", errno, strerror(errno));
        return SOCK_RET_FAILED;
    }
    // 设置接收超时1s
    if (setsockopt(socketFd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
        ERR("setsockopt SO_RCVTIMEO failed errno: (%d): %s", errno, strerror(errno));
        return SOCK_RET_FAILED;
    }

    // 关闭TCP延迟确认
    int controlFlag = 1;
    if ((m_socketOption & SOCKET_OPTION_BITSET_NODELAY) != 0) {
        if (setsockopt(socketFd, IPPROTO_TCP, TCP_NODELAY, &controlFlag, sizeof(controlFlag)) < 0) {
            ERR("setsockopt TCP_NODELAY failed errno: (%d): %s", errno, strerror(errno));
            return SOCK_RET_FAILED;
        }
    }

    if ((m_socketOption & SOCKET_OPTION_BITSET_QUICKACK) != 0) {
        if (setsockopt(socketFd, IPPROTO_TCP, TCP_QUICKACK, &controlFlag, sizeof(controlFlag)) < 0) {
            ERR("setsockopt TCP_QUICKACK failed errno: (%d): %s", errno, strerror(errno));
            return SOCK_RET_FAILED;
        }
    }
    return SOCK_RET_SUCCESS;
}

/*
 * @功能描述：创建客户端TCP套接字的帮助函数
 * @返回值：成功返回0，小于0表示失败
 */
int VmiTcpClientSocket::CreateSocket()
{
    SetEventNotice(nullptr);

    int sockFd = socket(PF_INET, SOCK_STREAM, 0);
    if (sockFd < 0) {
        ERR("Create socket failed, errno: (%d): %s", errno, strerror(errno));
        return SOCK_RET_FAILED;
    }

    if (SetOptions(sockFd) < 0) {
        (void)close(sockFd);
        return SOCK_RET_FAILED;
    }

    m_fd = sockFd;
    m_status = SOCKET_STATUS_INIT;
    return SOCK_RET_SUCCESS;
}

/*
 * @功能描述：连接TCP网络通信服务端
 * @返回值：0，连接成功；不等于0，连接失败
 */
int VmiTcpClientSocket::Connect()
{
    if (m_status == SOCKET_STATUS_RUNNING) {
        INFO("already connected");
        return SOCK_RET_SUCCESS;
    }

    int ret = CreateSocket();
    if (ret < 0) {
        return ret;
    }

    struct sockaddr_in localAddr;
    if (memset_s(&localAddr, sizeof(localAddr), 0, sizeof(localAddr)) != EOK) {
        ERR("error: memset localAddr failed");
        CloseSocket();
        return SOCK_RET_FAILED;
    }
    localAddr.sin_family = AF_INET;
    localAddr.sin_addr.s_addr = htonl(m_localIp);
    localAddr.sin_port = htons(m_localPort);

    struct sockaddr_in remoteAddr;
    if (memset_s(&remoteAddr, sizeof(remoteAddr), 0, sizeof(remoteAddr)) != EOK) {
        ERR("error: memset remoteAddr failed");
        CloseSocket();
        return SOCK_RET_FAILED;
    }
    remoteAddr.sin_family = AF_INET;
    remoteAddr.sin_addr.s_addr = htonl(m_remoteIp);
    remoteAddr.sin_port = htons(m_remotePort);

    int retryCount = 0;
    do {
        ret = connect(m_fd, (struct sockaddr*)&(remoteAddr), sizeof(remoteAddr));
        if (ret == 0) {
            INFO("connected to server");
            SetStatus(SOCKET_STATUS_RUNNING);
            break;
        }

        ERR("failed time(%d) when client(%d) connect to server, ret = %d, errno (%d) %s, try again...",
            retryCount, m_fd, ret, errno, strerror(errno));
        retryCount++;
        if (retryCount > RETRY_TIMES) {
            CloseSocket();
            break;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_INTERVAL_MS));
    } while (1);

    return ret;
}
